package fhq.hcmute.qrcode.encoder;

import java.io.UnsupportedEncodingException;

import fhq.hcmute.qrcode.common.DataMode;
import fhq.hcmute.qrcode.common.Version;

/**
 * @author Hien Nguyen Le
 * @Email: nglehien@gmail.com
 */
public class BitArray {
	private int[] bitArray;
	private int size;

	public BitArray() {
		bitArray = new int[0];
	}

	public BitArray(boolean... bs) {
		bitArray = new int[0];
		for (boolean i : bs) {
			append(i);
		}
	}

	public BitArray(String binaryValue) {
		bitArray = new int[0];
		append(binaryValue);
	}

	public BitArray(int size) {
		this.size = size;
	}

	private void checkToIncreaseSize(int size) {
		if (bitArray.length << 5 < size) {
			int[] newBitArray = new int[bitArray.length + 1];
			System.arraycopy(bitArray, 0, newBitArray, 0, bitArray.length);
			this.bitArray = newBitArray;
		}
	}

	public void append(boolean bit) {
		checkToIncreaseSize(this.size + 1);
		if (bit) {
			int pos = this.size >> 5;
			bitArray[pos] |= 1 << (size & 0x1F);
		}
		size++;
	}

	/**
	 * Append integer value with number of presentation for Binary value
	 * 
	 * @param numberPresentation
	 * @param value
	 */
	public void append(int numberPresentation, int value) {
		StringBuilder binaryValue = new StringBuilder(
				Integer.toBinaryString(value));
		if (value < 0) {
			binaryValue = binaryValue.delete(0, binaryValue.length() - 8);
		}
		int k = numberPresentation - binaryValue.length();
		for (int i = 0; i < k; i++) {
			binaryValue = binaryValue.insert(0, 0);
		}
		int newSize = binaryValue.length();
		checkToIncreaseSize(this.size + newSize);
		for (int i = 0; i < binaryValue.length(); i++) {
			char c = binaryValue.charAt(i);
			if (c == '1') {
				int pos = this.size >> 5;
				bitArray[pos] |= 1 << (size & 0x1F);
			}
			size++;
		}
	}

	/**
	 * Append integer value with number of presentation for Binary value
	 * 
	 * @param numberPresentation
	 * @param value
	 */
	public void append(int numberPresentation, int[] values) {
		for (int t = 0; t < values.length; t++) {
			int value = values[t];
			StringBuilder binaryValue = new StringBuilder(
					Integer.toBinaryString(value));
			if (value < 0) {
				binaryValue = binaryValue.delete(0, binaryValue.length() - 8);
			}
			int k = numberPresentation - binaryValue.length();
			for (int i = 0; i < k; i++) {
				binaryValue = binaryValue.insert(0, 0);
			}
			int newSize = binaryValue.length();
			checkToIncreaseSize(this.size + newSize);
			for (int i = 0; i < binaryValue.length(); i++) {
				char c = binaryValue.charAt(i);
				if (c == '1') {
					int pos = this.size >> 5;
					bitArray[pos] |= 1 << (size & 0x1F);
				}
				size++;
			}
		}
	}

	/**
	 * Append Mode and Character count.
	 * 
	 * @param dataMode
	 * @param version
	 */
	public void append(DataMode dataMode, Version version, String contents) {
		int numberPresentation = 4;
		int value = Integer.parseInt(dataMode.getValue(), 2);
		append(numberPresentation, value);

		numberPresentation = dataMode.getCharacterCount(version);
		if (dataMode.equals(DataMode.BINARY)) {
			try {
				value = contents.getBytes(Encoder.DEFAULT_BYTE_MODE_ENCODING).length;
			} catch (UnsupportedEncodingException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		} else {
			value = contents.length();
		}
		append(numberPresentation, value);
	}

	public void clear() {
		this.bitArray = new int[0];
		this.size = 0;
	}

	private boolean isBinary(String vales) {
		int count = 0;
		for (int i = 0; i < vales.length(); i++) {
			char c = vales.charAt(i);
			if (c == '0' || c == '1') {
				count++;
			}
		}
		if (count == vales.length()) {
			return true;
		}
		return false;
	}

	public void append(String binaryValue) {
		if (isBinary(binaryValue)) {
			int newSize = binaryValue.length();
			checkToIncreaseSize(this.size + newSize);
			for (int i = 0; i < binaryValue.length(); i++) {
				char c = binaryValue.charAt(i);
				if (c == '1') {
					int pos = this.size >> 5;
					bitArray[pos] |= 1 << (size & 0x1F);
				}
				size++;
			}
		}
	}

	public void append(BitArray bitArray) {
		int newSize = bitArray.size;
		checkToIncreaseSize(this.size + newSize);
		for (int i = 0; i < newSize; i++) {
			append(bitArray.getBitAt(i));
		}
	}

	public boolean getBitAt(int index) {
		return (this.bitArray[index >> 5] & (1 << (index & 0x1F))) != 0;
	}

	public BitArray getRange(int from, int to) {
		BitArray temp = new BitArray();
		for (int i = from; i <= to; i++) {
			temp.append(this.getBitAt(i));
		}
		return temp;
	}

	public int[] getBitArray() {
		return bitArray;
	}

	public int getSize() {
		return size;
	}

	public int[] getIntegerArray() {
		int[] values = new int[size / 8];
		String bitContents = toString();
		String[] temp = bitContents.split(" ");
		for (int i = 0; i < size / 8; i++) {
			values[i] = Integer.parseInt(temp[i], 2);
		}
		return values;
	}

	public int[] getIntegerArray(int delimit) {
		int[] values = new int[size / delimit];
		String bitContents = toString(delimit);
		String[] temp = bitContents.split(" ");
		for (int i = 0; i < size / delimit; i++) {
			values[i] = Integer.parseInt(temp[i], 2);
		}
		return values;
	}

	public byte[] getByteArray() {
		byte[] values = new byte[size / 8];
		int[] integerValues = getIntegerArray();
		for (int i = 0; i < size / 8; i++) {
			values[i] = (byte) integerValues[i];
		}
		return values;
	}

	public int[] getBinaryArray() {
		int[] values = new int[size];
		String bitContents = toString(0);
		String[] temp = bitContents.split(" ");
		for (int i = 0; i < temp.length; i++) {
			values[i] = Integer.parseInt(temp[i]);
		}
		return values;
	}

	public int[] getIntegerArray(int from, int to) {
		int[] values = new int[to - from + 1];
		String bitContents = toString();
		String[] temp = bitContents.split(" ");
		for (int i = from; i <= to; i++) {
			if (i <= to) {
				values[i - from] = Integer.parseInt(temp[i], 2);
			}
		}
		return values;
	}

	public void xor(String binaryValue) {
		BitArray bitArray = new BitArray(binaryValue);
		this.xor(bitArray);
	}

	public void xor(BitArray bitArray) {
		BitArray temp = new BitArray();
		int offset = Math.abs(this.getSize() - bitArray.getSize());
		if (this.size > bitArray.size) {
			for (int i = 0; i < this.size; i++) {
				if (i + bitArray.size >= this.size) {
					boolean xor = bitArray.getBitAt(i - offset)
							^ this.getBitAt(i);
					temp.append(xor);
				} else {
					temp.append(this.getBitAt(i));
				}
			}
		} else {
			for (int i = 0; i < bitArray.size; i++) {
				if (i + this.size >= bitArray.size) {
					boolean xor = bitArray.getBitAt(i)
							^ this.getBitAt(i - offset);
					temp.append(xor);
				} else {
					temp.append(bitArray.getBitAt(i));
				}
			}
		}
		this.bitArray = temp.bitArray;
		this.size = temp.size;
	}

	public String toString() {
		StringBuilder stringBuilder = new StringBuilder();
		for (int i = 0; i < this.size; i++) {
			if ((i & 7) == 0 && i != 0) {
				stringBuilder.append(" ");
			}
			if (getBitAt(i)) {
				stringBuilder.append("1");
			} else {
				stringBuilder.append("0");
			}
		}
		return stringBuilder.toString();
	}

	public String toString(String tr, String fa) {
		StringBuilder stringBuilder = new StringBuilder();
		for (int i = 0; i < this.size; i++) {
			if ((i & 7) == 0 && i != 0) {
				stringBuilder.append(" ");
			}
			if (getBitAt(i)) {
				stringBuilder.append(tr);
			} else {
				stringBuilder.append(fa);
			}
		}
		return stringBuilder.toString();
	}

	public String toString(int n) {
		StringBuilder stringBuilder = new StringBuilder();
		for (int i = 0; i < this.size; i++) {
			if (n != -1) {
				if ((i & n) == 0 && i != 0) {
					stringBuilder.append(" ");
				}
			}
			if (getBitAt(i)) {
				stringBuilder.append("1");
			} else {
				stringBuilder.append("0");
			}
		}
		return stringBuilder.toString();
	}

	public static int NONE_DELIMIT = -1;
}
